#include <unistd.h>
#include <stdint.h>
#include <stddef.h>
#include <limits.h>
#include <errno.h>
#include <string.h>
#include <sys/types.h>
#include <stdlib.h>
#include <stdio.h>
#include <syslog.h>

#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>

#include "thread_hal.h"
#include "libwebsockets.h"
#include "websocket_client.h"

#define SERVER_IP      "127.0.0.1"

#define MAX_CLIENT_PAYLOAD 128

#define RING_BUF_EMPTY 0
#define RING_BUF_FULL  1

typedef struct {
    char buf[LWS_PRE+MAX_CLIENT_PAYLOAD];
    int  len;
    int status;
}Ring_Buf;
#define MAX_RING_BUFFER_CNT  100

Ring_Buf  ring_rx[MAX_RING_BUFFER_CNT];
Ring_Buf  ring_tx[MAX_RING_BUFFER_CNT];

WebSock_Status client_state = Socket_Init;
struct lws *g_lws_client;
struct lws_context *g_lws_context;
char peer_name[128];
char peer_ip[128];

int callback_cmd_client(struct lws *wsi, enum lws_callback_reasons reason, void *user,
	      void *in, size_t len)
{
	int n;

	switch (reason) {

	/* when the callback is used for client operations --> */
    case LWS_CALLBACK_HTTP:
        lwsl_notice("Callback LWS_CALLBACK_HTTP\n");
        break;        
    case LWS_CALLBACK_WSI_CREATE:
        lwsl_notice("Create LWS_CALLBACK_WSI_CREATE\n");
        break;        
    case LWS_CALLBACK_WSI_DESTROY:
        lwsl_notice("Destory LWS_CALLBACK_WSI_DESTROY\n");
        break;
//    case LWS_CALLBACK_PROTOCOL_INIT:
    case LWS_CALLBACK_PROTOCOL_DESTROY:
        lwsl_notice("Protocal Destory LWS_CALLBACK_PROTOCOL_DESTROY\n");
        break;
	case LWS_CALLBACK_CLOSED:
		lwsl_notice("Close LWS_CALLBACK_CLOSED\n");
		client_state = Socket_Close;
		break;

	case LWS_CALLBACK_CLIENT_CONNECTION_ERROR:
		lwsl_notice("Connect LWS_CALLBACK_CLIENT_CONNECTION_ERROR\n");
		client_state = Socket_Error;
		break;

	case LWS_CALLBACK_CLIENT_ESTABLISHED:        
        lws_get_peer_addresses(g_lws_client, lws_get_socket_fd(g_lws_client),
                        peer_name, sizeof peer_name, peer_ip, sizeof(peer_ip));
        HAL_INFO("peer_name is %s ,  peer_ip is %s \n",peer_name , peer_ip);

        lws_callback_on_writable(wsi);
		lwsl_notice("Client has connected LWS_CALLBACK_CLIENT_ESTABLISHED\n");
		client_state = Socket_Connected;
		break;

	case LWS_CALLBACK_CLIENT_RECEIVE:
		lwsl_notice("Client RX: Size %d  \n %s \n", len , (char*)in);
        for(int i = 0; i < MAX_RING_BUFFER_CNT;i++){

            if(RING_BUF_EMPTY == ring_rx[i].status ){
                if(len <= MAX_CLIENT_PAYLOAD){
                    memcpy(&ring_rx[i].buf[LWS_PRE], in, len);
                    ring_rx[i].len = len;
                    ring_rx[i].status = RING_BUF_FULL;
                    break;
                }else{
                    HAL_ERR("(%s:%d) buffer two small %d, recieve data %d",
                        __FUNCTION__,__LINE__,RING_BUF_FULL, (int)len);
                }
            }
        }
		break;

	case LWS_CALLBACK_CLIENT_WRITEABLE:
		/* we will send our packet... */
        for(int i = 0; i < MAX_RING_BUFFER_CNT;i++){
            if(RING_BUF_FULL == ring_tx[i].status){
                HAL_MSG("Client starting to send data\n");
                HAL_INFO("The Data is: %s \n" , &ring_tx[i].buf[LWS_PRE]);
		        n = lws_write(wsi, &ring_tx[i].buf[LWS_PRE], ring_tx[i].len, LWS_WRITE_TEXT);
                ring_tx[i].len = 0;
                ring_tx[i].status = RING_BUF_EMPTY;
            }
        }
        
        lws_callback_on_writable(g_lws_client);
		break;
	case LWS_CALLBACK_RECEIVE_CLIENT_HTTP:
		{
			char buffer[1024 + LWS_PRE];
			char *px = buffer + LWS_PRE;
			int lenx = sizeof(buffer) - LWS_PRE;

			lwsl_notice("LWS_CALLBACK_RECEIVE_CLIENT_HTTP\n");

			/*
			 * Often you need to flow control this by something
			 * else being writable.  In that case call the api
			 * to get a callback when writable here, and do the
			 * pending client read in the writeable callback of
			 * the output.
			 */
			if (lws_http_client_read(wsi, &px, &lenx) < 0)
				return -1;
			while (lenx--)
				putchar(*px++);
		}        
	case LWS_CALLBACK_CLIENT_CONFIRM_EXTENSION_SUPPORTED:
		/* reject everything else except permessage-deflate */
		if (0== strcmp(in, "permessage-deflate") || 0==strcmp(in, "deflate-frame"))
			return 1;
		break;

	default:
		break;
	}

	return 0;
}

/* list of supported protocols and callbacks */
static struct lws_protocols protocols[] = {
	{
		FISH_PROTOCOL_NAME,
//        "http-only",
		callback_cmd_client,
		0,
		MAX_CLIENT_PAYLOAD,
	},
	{ NULL, NULL, 0, 0 } /* end */
};

static const struct lws_extension exts[] = {
	{
		"permessage-deflate",
		lws_extension_callback_pm_deflate,
		"permessage-deflate; client_max_window_bits"
	},
	{
		"deflate-frame",
		lws_extension_callback_pm_deflate,
		"deflate_frame"
	},
	{ NULL, NULL, NULL /* terminator */ }
};


int websocket_client_create(char *server_ip , int port)
{
	struct lws_context_creation_info info;
	struct lws_client_connect_info client;
    int ietf_version = -1;
    char ads_port[128];
	int debug_level = LLL_ERR | LLL_WARN|LLL_NOTICE;
	int syslog_options = LOG_PID | LOG_PERROR;

    memset(&info, 0, sizeof(info));
    memset(&client, 0, sizeof(client));

    /* tell the library what debug level to emit and to send it to syslog */
	/* we will only try to log things according to our debug_level */
	setlogmask(LOG_UPTO (LOG_DEBUG));
	openlog("lwsts", syslog_options, LOG_DAEMON);
    lws_set_log_level(debug_level, lwsl_emit_syslog);

    /*
     * create the websockets context.  This tracks open connections and
     * knows how to route any traffic and which protocol version to use,
     * and if each connection is client or server side.
     *
     * For this client-only demo, we tell it to not listen on any port.
     */
    lwsl_notice("Built to support client operations\n");

	info.port = CONTEXT_PORT_NO_LISTEN;
	info.protocols = protocols;
	info.gid = -1;
	info.uid = -1;
    info.options |= LWS_SERVER_OPTION_VALIDATE_UTF8;

	g_lws_context = lws_create_context(&info);
	if (g_lws_context == NULL) {
		lwsl_err("libwebsocket init failed\n");
		fprintf(stderr, "Creating libwebsocket context failed\n");
		return HAL_ERROR;
	}

	snprintf(ads_port, sizeof(ads_port), "%s:%u",
		 server_ip, FISH_PROTOCOL_PORT & 65535);

    lwsl_notice("Running in client mode\n");
	lwsl_notice("Connecting to %s...\n", ads_port);

    client.port = FISH_PROTOCOL_PORT;
    client.address = server_ip;

	client.context = g_lws_context;
	client.ssl_connection = 0;

	client.path = "/";
	client.host = ads_port;
	client.origin = ads_port;
	client.protocol = protocols[0].name;

	client.ietf_version_or_minus_one = ietf_version;
	client.client_exts = exts;    

    g_lws_client = lws_client_connect_via_info(&client);

    if (NULL == g_lws_client) {
        lwsl_err("client failed to connect to server %s , port %d \n", server_ip,FISH_PROTOCOL_PORT);
        return HAL_CONNECT_FAIL;
    }

    client_state = Socket_Created;

    return HAL_SUCCESS;
}

int websocket_client_destory()
{
	lwsl_notice("libwebsockets  Client exited cleanly\n");
	lws_context_destroy(g_lws_context);    

    client_state = Socket_Init;

    return HAL_SUCCESS;
}

int websocket_client_active_rw_action()
{
    int ret;

    ret = lws_service(g_lws_context, 500);
    
    return HAL_SUCCESS;
}
/*
return the read data length
*/
int  websocket_client_read_data(char *p_buf, int len)
{
    int i ;
    for(i = 0; i < MAX_RING_BUFFER_CNT ; i++){
        if(RING_BUF_FULL == ring_rx[i].status)
            break;
    }

    /*no data in read qeue*/
    if(i == MAX_RING_BUFFER_CNT)
        return 0;

    if(len < ring_rx[i].len){
        HAL_ERR("(%s:%d) Data len %d ,Read buffer %d. No Enough Buffer for Read Data.\n",
            __FUNCTION__,__LINE__ , ring_rx[i].len , len);
        return HAL_NO_MEMORY;
    }

    memcpy(p_buf, &ring_rx[i].buf[LWS_PRE], ring_rx[i].len);
    ring_rx[i].status = RING_BUF_EMPTY;

    return ring_rx[i].len;

}

int websocket_client_write_data(char *p_buf, int len)
{
    int i ;
    for(i = 0; i < MAX_RING_BUFFER_CNT ; i++){
        if(RING_BUF_EMPTY == ring_tx[i].status)
            break;
    }

    if(i == MAX_RING_BUFFER_CNT){
        HAL_ERR("(%s:%d) Write Queue Full, please Waiting.\n",
            __FUNCTION__,__LINE__);
        return HAL_QUEUE_FULL;
    
    } 
    if(len > MAX_CLIENT_PAYLOAD){
        HAL_ERR("(%s:%d) Data len %d . No Enough Buffer for Write Data.\n",
            __FUNCTION__,__LINE__ , len);
        return HAL_NO_MEMORY;
    }
    memcpy(&ring_tx[i].buf[LWS_PRE], p_buf, len);
    ring_tx[i].len = len;
    ring_tx[i].status = RING_BUF_FULL;

    if(Socket_Connected == client_state)
        lws_callback_on_writable(g_lws_client);

    return HAL_SUCCESS;
}

WebSock_Status websocket_client_get_status()
{
    return client_state;
}



